﻿#region References
using System;
using System.Collections.Generic;
using System.Collections;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using JigLibX.Physics;
using JigLibX.Geometry;
using JigLibX.Collision;
using Xen;
using Xen.Camera;
using Xen.Graphics;
using Xen.Input.State;
using Xen.Ex.Material;
using Xen.Graphics.State;
using Xen.Ex.Graphics.Content;
using Xen.Ex.Graphics;
using Xen.Ex.Graphics2D;
using Ness.Engine.Entities;
using Ness.Engine.Graphics;
using Ness.Engine.GUI;
using Ness.Engine.Lua;
using Ness.Engine.Cameras;
using Ness.Engine.Entities.Primitives;
using Ness.Engine.Interact;
#endregion

namespace Ness.Engine
{
    /// <summary>
    /// This class handles display, bits of lua, lights and effects
    /// </summary>
    class Engine : Application
    {
        #region Private variables
        private KeyboardState oldKeyboardState;
        #endregion

        #region Public variables
        public Lighting Lighting;
        public DrawTargetScreen drawToScreen;
        public ControlledCamera3D camera;
        public PhysicsSystem physicSystem;
        public LuaParser lua;
        public Hashtable cached = new Hashtable();
        public List<EventTrigger> eventTriggers = new List<EventTrigger>();
        public Hashtable frameListeners = new Hashtable();
        public int entityCount;
        #endregion

        /// <summary>
        /// Init method, called when the game is run. This sets up everything the engine needs.
        /// </summary>
        protected override void Initialise()
        {
            //Initialise the debugger for CMD
            Debugger.OutputTo("cmd");
            ((Game)this).Content.RootDirectory = "Content";

            //Output init to console
            Debugger.WriteLine("Initialising Ness Engine version: " + GetBuild());

            //Initialise Lua
            lua = new LuaParser(this);

            //Initialise physics
            InitializePhysics();

            //Init base classes
            camera = new FreeCamera(this, this.UpdateManager);

            Lighting = new Lighting();
            drawToScreen = new DrawTargetScreen(this, camera);
            drawToScreen.ClearBuffer.ClearColour = Color.CornflowerBlue;

            Debugger.WriteColour("Model caching started: *_xna.xnb", ConsoleColor.Green);
            CacheRecursive("Content/models", ((Game)this).Content);

            MeshObject box = new MeshObject(this, "models/debug/debug", Vector3.Zero);
            AddToScene(box);

          //  Ragdoll ness = new Ragdoll(this, "models/testmodel", Vector3.Zero+Vector3.Up*300);
          //  AddToScene(ness);

            BoxObject boxa = new BoxObject(this, "models/box", Vector3.One, Matrix.Identity, Vector3.Zero + Vector3.Up * 15);
            AddToScene(boxa);

            AddToScene(new DebugDrawer(this));
        }

        /// <summary>
        /// Initialises the player input and centers mouse
        /// </summary>
        /// <param name="playerInput">Ignore, passed automatically</param>
        protected override void InitialisePlayerInput(Xen.Input.PlayerInputCollection playerInput)
        {
            if (playerInput[PlayerIndex.One].ControlInput == Xen.Input.ControlInput.KeyboardMouse)
                playerInput[PlayerIndex.One].InputMapper.CentreMouseToWindow = true;
        }

        private void CacheRecursive(string sDir, ContentManager manager)
        {
            int strlen = ("Content/").Length;
            try
            {
                foreach (string d in Directory.GetDirectories(sDir))
                {
                    foreach (string f in Directory.GetFiles(d, "*_xna.xnb"))
                    {
                        string filePath = f.Substring(0, f.Length - (".xnb").Length).Substring(strlen);
                        Debugger.WriteColour("Caching model: " + filePath, ConsoleColor.Green);
                        cached.Add("model_" + filePath.Replace("\\", "/"), manager.Load<Model>(filePath));
                    }
                    CacheRecursive(d, manager);
                }
            }
           catch (System.Exception excpt)
           {
                Debugger.WriteError(excpt.Message);
           }
        }

        /// <summary>
        /// Initialises the game's physics engine for processing
        /// </summary>
        private void InitializePhysics()
        {
            //Base physics
            physicSystem = new PhysicsSystem();
            physicSystem.EnableFreezing = true;
            physicSystem.SolverType = PhysicsSystem.Solver.Normal;

            //Collision
            physicSystem.CollisionSystem = new CollisionSystemSAP();
            physicSystem.CollisionSystem.UseSweepTests = true;
            physicSystem.NumCollisionIterations = 8;
            physicSystem.NumContactIterations = 8;
            physicSystem.NumPenetrationRelaxtionTimesteps = 15;
        }

        /// <summary>
        /// Draw method will draw all objects to the render target
        /// </summary>
        protected override void Draw(DrawState state)
        {
            //Update window title
            this.Window.Title = "Running for: " + Math.Round(state.TotalTimeSeconds) + " seconds";
            this.Window.Title = this.Window.Title + " " + string.Format("{0} FPS", state.ApproximateFrameRate);
            this.Window.Title = this.Window.Title + " " + string.Format("{0} Entities", entityCount);

            drawToScreen.Draw(state);
        }

        /// <summary>
        /// Update method will update physics and controllers
        /// </summary>
        protected override void Update(UpdateState state)
        {
            //Check for keybinds
            KeyboardState keys = Keyboard.GetState();
            Keys[] keysDown = keys.GetPressedKeys();
            if (keysDown.Length > 0)
            {
                if (!oldKeyboardState.IsKeyDown(keysDown[0]))
                {
                    Binds.DoBind(keysDown[0].ToString());
                }
            }
            oldKeyboardState = keys;

            //Check for event/regional triggers
            foreach (EventTrigger trigger in eventTriggers)
            {
                trigger.CheckActivate(camera.Position);
            }

            //Activate frame listeners
            foreach (DictionaryEntry func in frameListeners)
            {
                if ((func.Value as LuaInterface.LuaFunction) != null)
                {
                    (func.Value as LuaInterface.LuaFunction).Call();
                }
            }

            physicSystem.Integrate(1.0f / 60.0f);
        }


        public void AddLightToScene(Vector3 lightPosition, Color lightColor, Color lightSpecularColour, float lightHalfFalloffDistance, bool perPixel)
        {
            IDraw lightGeometry = null;

            //interface to the light about to be created
            IMaterialPointLight light = Lighting.CreateLight(lightPosition, lightColor, lightSpecularColour, lightHalfFalloffDistance, perPixel);
            light.ConstantAttenuation = 0.25f;

            //create the light geometry (a sphere)
            if (lightGeometry == null)
                lightGeometry = new Xen.Ex.Geometry.Sphere(Vector3.One, 8, true, false, false);

            //visually show the light with a light drawer
            IDraw lightSourceDrawer = new LightSourceDrawer(lightPosition, lightGeometry, lightColor);

            //add the light geometry to the screen
            drawToScreen.Add(lightSourceDrawer);
        }

        public object GetCachedAsset(string key)
        {
            if (cached.Contains(key))
            {
                return cached[key] as object;
            }
            else
            {
                Debugger.WriteError("Asset not in cache: " + key);
                return null;
            }
        }

        /// <summary>
        /// Returns the current engine build, useful for debugging
        /// </summary>
        public string GetBuild()
        {
            return this.GetType().Assembly.GetName().Version.ToString();
        }

        /// <summary>
        /// Will add an entity to the scene
        /// </summary>
        public void AddToScene(IDraw ent)
        {
            try
            {
                //Add the entity to the scene
                drawToScreen.Add(ent);
                Debugger.WriteLine("Added entity (" + ent.ToString() + ") to scene");
                entityCount++;
            }
            catch(Exception e)
            {
                Debugger.WriteError("Unable to add entity (" + ent.ToString() + ") to scene. Reason: " + e.Message);
            }
        }
    }
}
